# coding: utf-8
# @Author: cyl
# @File: 11魔法方法.py
# @Time: 2024/07/21 10:03:10


# 举个例子
class Company:
    def __init__(self, employee_list):
        self.employee = employee_list
    
    def __getitem__(self, item):
        return self.employee[item]
        
    def __len__(self):
        print("输出employee的长度")
        return len(self.employee)

company = Company(['a', 'b', 'c'])
# ps: 如果我们没有在Company类中定义魔法方法__getitem__, 则会报异常：TypeError: Company is not iterable
for com in company:
    print(com) # a b c
# ps: 同样的如果没有定义魔法方法__getitem__, 则仍然会报错；也就是说魔法方法会影响函数或者类、方法本身
print(len(company)) # "输出employee的长度" 3


"""
    内置__attr__属性
"""
class Foo:
    x = 1

    def __init__(self, y):
        self.y = y

    # 在调用该对象不存在的属性时候才会触发运行该方法
    def __getattr__(self, item):
        print('执行__getattr__方法')

    # 在删除该对象的属性时候会触发运行该方法
    def __delattr__(self, item):
        self.y = 0
        print('执行__delattr__方法')

    # 在设置该对象的属性时候会触发运行该方法
    def __setattr__(self, key, value):
        self.__dict__[key] = value
        print('执行__setattr__方法')


f = Foo(10)
print(f.x)   # 1
print(f.y)   # 10
f.__setattr__('z', 100)
print(f.__dict__)
del f.y
print(f.__dict__)

setattr(f, "hhh", 10000)
print(f.__dict__)


"""
    __str__与__repr__、__format__

    1. __str__
        自定义对象的显示方式, 返回一个对象的描述信息; __str__方法需要返回一个字符串, 当做这个对象的描写
    2. __repr__
        执行 print(clangs) 等同于执行 print(clangs.__repr__()), 程序的输出结果是一样的（输出的内存地址可能不同）
    如果__str__未定义, 那么就会按__repr__格式输出

    3. __format__
        自定义字符串的输出格式
"""
class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return "名字:%s, 年龄:%d" % (self.name, self.age)

    # 如果类当中有指定__repr__, 按该方法内代码输出对应格式
    def __repr__(self):
        return "年龄:%d, 名字:%s" % (self.age, self.name)

    def eat(self):
        print("%s在吃鱼...." % self.name)
  

tom = Foo("汤姆", 30)
print(tom)   # 名字:汤姆, 年龄:30

format_style = {
    'ymd': '{0.year}{0.month}{0.day}',
    'm-d-y': '{0.month}-{0.day}-{0.year}',
    'y:m:d': '{0.year}:{0.month}:{0.day}'
}


class FormatData:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in format_style:
            fm = format_style['ymd']
        else:
            fm = format_style[format_spec]
        return fm.format(self)


d = FormatData(2019, 10, 12)
f_data_1 = format(d)
f_data_2 = format(d, 'ymd')
f_data_3 = format(d, 'm-d-y')
f_data_4 = format(d, 'y:m:d')
f_data_5 = format(d, 'mdd')

print(f_data_1)
print(f_data_2)
print(f_data_3)
print(f_data_4)
print(f_data_5)


"""
    item系列: __setitem__ __getitem__ __delitem__
"""
class Foo:
    x: int = 1

    def __init__(self):
        self.y = 10
        self.z = 100

    def __getitem__(self, item):
        print('__getitem__')
        return self.__dict__.get(item)

    def __setitem__(self, key, value):
        print('__setitem__')
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('__delitem__')
        if self.__dict__:
            self.__dict__.pop(key)


f = Foo()
print(f.__dict__)
f['name'] = 'egg'
print(f.__dict__)
print(f['name'])
del f['name']

print(f.x)



"""
__init__ 和 __new__ ********
"""
# 1.首先用法不同, __new__()用于创建实例, 所以该方法是在实例创建之前被调用, 它是类级别的方法, 是个静态方法；
#   而 __init__() 用于初始化实例, 所以该方法是在实例对象创建后被调用, 它是实例级别的方法, 用于设置对象属性的一些初始值
# 　由此可知, __new__()在__init__() 之前被调用
# 　如果__new__() 创建的是当前类的实例, 会自动调用__init__()函数, 通过return调用的__new__()的参数cls来保证是当前类实例;
# 　如果是其他类的类名, 那么创建返回的是其他类实例, 就不会调用当前类的__init__()函数
class Student:
    def __new__(cls, name, height):
        print("class.__new__ called")
        return super(Student, cls).__new__(cls)

    def __init__(self, name, height):
        print("class.__init__ called")
        self.name = name
        self.height = height
    
    def __str__(self):
        return "name: {}, height: {}".format(self.name, self.height)

    def __del__(self):
        print("删除该实例对象...")

xiaoming = Student("xiaoming", 120)
print(xiaoming)
del xiaoming
# 输出
# class.__new__ called
# class.__init__ called
# "name: xiaoming, height: 120"
# 除该实例对象...

# 2.其次传入参数不同
# 　　__new__()至少有一个参数cls, 代表当前类, 此参数在实例化时由Python解释器自动识别；
# 　　__init__()至少有一个参数self, 就是这个__new__()返回的实例, __init__()在__new__()的基础上完成一些初始化的操作
# 　　
# 3.返回值不同
# 　　__new__()必须有返回值, 返回实例对象；
# 　　__init__()不需要返回值

# 4. __new__()方法主要用于继承一些不可变的class, 比如int, str, tuple, 提供一个自定义这些类的实例化过程的途径, 通过重载__new__()方法来实现
class A(int):
    def __new__(cls, value):
        return super(A, cls).__new__(cls, abs(value))

a = A(-10)
print(a)   # 10

# 5. __new__()方法还可以用来实现单例模式, 也就是使每次实例化时只返回同一个实例对象
class Single(object):
    def __new__(cls):
        if not hasattr(cls, "instance"):
            cls.instance = super(Single, cls).__new__(cls)
        return cls.instance


obj1 = Single()
obj2 = Single()
print(id(obj1), id(obj2)) # 10000 10000
print(obj1 is obj2) # True
obj1.attr = "123"
print(obj1.attr, obj2.attr) # "123" "123"


"""
总结: class被实例化的时候, 其实做了很多操作(一个类的全生命周期)
1、创建实例 __new__
2、初始化实例对象 __init__
3、执行其他内置魔法方法: __str__ __repr__ __format__ __getattr__ __len__
4、执行自定义的方法或函数
5、当类实例不在被使用时, 调用del回收: __del__
"""
